Conversation
📝 WalkthroughWalkthroughIntroduces a tick-synchronized event system: new TickDispatcher, TickListener, and TickWaiter manage game-tick callbacks and waiters; Global gains tick-aligned sleep/wait utilities; Script adds a tick-scheduled helper; Microbot exposes the TickDispatcher; BlockingEventManager exposes a read-only isBlocking() method; documentation updated. Changes
Sequence Diagram(s)sequenceDiagram
participant Script as Script Thread
participant TickD as TickDispatcher
participant TickW as TickWaiter
participant Cond as Condition<br/>(Lock)
participant Client as Client Thread<br/>(GameTick Event)
Script->>TickD: registerWait(condition, maxTicks, timeout)
TickD->>TickW: create & queue TickWaiter
TickD-->>Script: return TickWaiter
Script->>TickW: await()
TickW->>Cond: acquire lock + await signal
Note over TickW: Script thread blocked
Client->>TickD: onGameTick(tickCount)
TickD->>TickD: invoke onGameTick listeners
TickD->>TickD: invoke onPostTick listeners
TickD->>TickW: evaluateAndSignal(currentTick)
TickW->>TickW: check condition / tick cap
alt Condition Met or Cap/Timeout
TickW->>Cond: signalAll()
end
Cond-->>TickW: lock reacquired
TickW-->>Script: return conditionMet
sequenceDiagram
participant Script as Script Thread
participant Global as Global.sleepUntil()
participant TickD as TickDispatcher
participant TickW as TickWaiter
participant Client as Client Thread
Script->>Global: sleepUntil(condition, timeoutMs)
Global->>Global: compute deadline & immediate check
alt Condition True
Global-->>Script: return true
else
Global->>TickD: registerWait(condition, 0, timeoutMs)
TickD->>TickW: create & queue waiter
Global->>TickW: await()
Note over TickW: Script blocked until tick or timeout
Client->>TickD: onGameTick(tick)
TickD->>TickW: evaluateAndSignal(tick)
TickW->>TickW: signal completion
TickW-->>Global: return boolean
Global-->>Script: return result
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/Global.java (1)
188-209: Inconsistent return type between overloads.
sleepUntilNextTick()returnsvoidwhilesleepUntilNextTick(long)returnsboolean. This minor inconsistency could confuse callers who might expect to check timeout status from the no-arg version.♻️ Consider returning boolean for consistency
-public static void sleepUntilNextTick() { - sleepUntilNextTick(2000); -} +public static boolean sleepUntilNextTick() { + return sleepUntilNextTick(2000); +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/Global.java` around lines 188 - 209, The no-arg overload sleepUntilNextTick() currently returns void while sleepUntilNextTick(long) returns boolean; change sleepUntilNextTick() to return boolean and propagate the result from sleepUntilNextTick(2000) so callers can observe timeout status, update its Javadoc to document the boolean return, and adjust any call sites of sleepUntilNextTick() that assumed void to handle/ignore the boolean as appropriate; refer to the methods sleepUntilNextTick() and sleepUntilNextTick(long) and the TickWaiter usage for locating the code to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickDispatcher.java`:
- Around line 197-204: The registerWait method registers a TickWaiter with
startTick taken from the cached currentTick which may be zero before the first
GameTick and cause immediate timeouts; modify registerWait in TickDispatcher to
use the live client tick (client.getTickCount()) as the startTick (or fall back
to client.getTickCount() only when currentTick == 0) when constructing the
TickWaiter so that TickWaiter startTick reflects the real client tick counter
(keep references to TickWaiter and its constructor signature intact and only
change the value passed as startTick).
In
`@runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickWaiter.java`:
- Around line 91-96: The unconditional-wait branch in TickWaiter (where
condition == null) currently calls signalCompletion(true) immediately; change it
to count ticks and only resolve when the elapsed ticks reach maxTicks: record
the start tick (or use an existing startTick field), compute elapsed =
currentTick - startTick (or increment an internal tick counter each tick), and
call signalCompletion(true) only when elapsed >= maxTicks; otherwise just return
false so the waiter keeps waiting; update the logic around currentTick, maxTicks
and signalCompletion in the TickWaiter tick handler to mirror the
conditional-wait path.
---
Nitpick comments:
In
`@runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/Global.java`:
- Around line 188-209: The no-arg overload sleepUntilNextTick() currently
returns void while sleepUntilNextTick(long) returns boolean; change
sleepUntilNextTick() to return boolean and propagate the result from
sleepUntilNextTick(2000) so callers can observe timeout status, update its
Javadoc to document the boolean return, and adjust any call sites of
sleepUntilNextTick() that assumed void to handle/ignore the boolean as
appropriate; refer to the methods sleepUntilNextTick() and
sleepUntilNextTick(long) and the TickWaiter usage for locating the code to
change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 77befb3c-de0d-4c7c-a011-b56d7b2cc4e2
📒 Files selected for processing (8)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/BlockingEventManager.javarunelite-client/src/main/java/net/runelite/client/plugins/microbot/CLAUDE.mdrunelite-client/src/main/java/net/runelite/client/plugins/microbot/Microbot.javarunelite-client/src/main/java/net/runelite/client/plugins/microbot/Script.javarunelite-client/src/main/java/net/runelite/client/plugins/microbot/util/Global.javarunelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickDispatcher.javarunelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickListener.javarunelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickWaiter.java
...lite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickDispatcher.java
Show resolved
Hide resolved
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickWaiter.java
Show resolved
Hide resolved
There was a problem hiding this comment.
6 issues found across 8 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickDispatcher.java">
<violation number="1" location="runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickDispatcher.java:143">
P2: Orphaned waiters are cleared without being signaled on logout, leaving script threads blocked until their wall-clock timeout expires. Iterate over the waiters and signal their completion before clearing the queue. This requires adding a package-private `cancel()` method (or similar) to `TickWaiter` that calls `signalCompletion(false)`, then calling it here for each waiter before `clear()`.</violation>
<violation number="2" location="runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickDispatcher.java:199">
P2: Edge case: if `registerWait` is called before the first `GameTick` event fires, `currentTick` is still 0 while the real tick count (from `client.getTickCount()`) may already be large. When `evaluateAndSignal` runs with the real tick count, `(currentTick - startTick) >= maxTicks` will be true immediately, causing the waiter to timeout on its very first evaluation. Use `client.getTickCount()` instead of `currentTick` here to capture the accurate tick count at registration time.</violation>
</file>
<file name="runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickWaiter.java">
<violation number="1" location="runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickWaiter.java:84">
P1: Tick timeout check fires before condition evaluation, so on the boundary tick a waiter that should succeed is instead reported as timed-out. Move the timeout check after the condition evaluation (or the unconditional-wait check) so the condition gets one last chance on the final allowed tick.</violation>
<violation number="2" location="runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickWaiter.java:91">
P1: Bug: unconditional waits (`condition == null`) resolve immediately on the first tick, ignoring `maxTicks`. For `sleepForTicks(3)`, which calls `registerWait(null, 3, timeout)`: on the first tick evaluation, the tick-limit check `(currentTick - startTick) >= 3` is false (only 1 tick elapsed), so it falls through to the `condition == null` branch which immediately signals completion. This means `sleepForTicks(N)` always waits only 1 tick regardless of N.
The unconditional path should return `false` to keep waiting, letting only the tick-limit check above resolve it when enough ticks have elapsed.</violation>
<violation number="3" location="runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickWaiter.java:135">
P2: When `await()` returns due to wall-clock timeout, `completed` is not set to `true`, so the dispatcher continues holding and evaluating this waiter every tick. If `maxTicks` is 0, the orphaned waiter stays in the queue indefinitely. Set `completed = true` before returning so that a follow-up `evaluateAndSignal` check can detect and remove it, or signal completion directly.</violation>
</file>
<file name="runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/Global.java">
<violation number="1" location="runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/Global.java:85">
P1: Busy-loop when `TickDispatcher` is null. If `sleepUntilNextTick` returns immediately (dispatcher not yet initialized), this loop spins with zero delay, pegging the CPU at 100% for the entire timeout. Add a fallback `sleep` when tick-based waiting is unavailable.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickWaiter.java
Outdated
Show resolved
Hide resolved
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/Global.java
Outdated
Show resolved
Hide resolved
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickWaiter.java
Show resolved
Hide resolved
...lite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickDispatcher.java
Show resolved
Hide resolved
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickWaiter.java
Show resolved
Hide resolved
...lite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickDispatcher.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
🧹 Nitpick comments (2)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickDispatcher.java (1)
41-43: Consider adding a shutdown method for completeness.The subscriber handles are stored but never used for unregistration. While this is fine for an application-lifetime singleton, adding a
shutdown()method that callseventBus.unregister(gameTickSubscriber)etc. would make the class more testable and complete.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickDispatcher.java` around lines 41 - 43, Add a shutdown method to TickDispatcher that unregisters the retained subscribers: implement public void shutdown() { eventBus.unregister(gameTickSubscriber); eventBus.unregister(gameStateChangedSubscriber); } so tests and shutdown logic can call TickDispatcher.shutdown() to cleanly remove the subscribers stored in gameTickSubscriber and gameStateChangedSubscriber; ensure the method is public and idempotent (safe to call multiple times).runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/Global.java (1)
222-224: Consider documenting the timeout formula rationale.The default timeout
ticks * 800L + 2000assumes ~800ms worst-case per tick plus a 2-second buffer. This is reasonable for handling server lag, but a brief inline comment would help future maintainers understand the magic numbers.📝 Suggested documentation
public static boolean sleepForTicks(int ticks) { + // 800ms accounts for worst-case tick timing under server lag; +2000ms buffer for safety return sleepForTicks(ticks, ticks * 800L + 2000); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/Global.java` around lines 222 - 224, Add a short inline comment to the sleepForTicks(int) method explaining the timeout formula used in the default argument (ticks * 800L + 2000), e.g., note that 800L represents a conservative ~800ms worst‑case per game tick and the +2000 is a 2s safety buffer to handle server lag; place the comment adjacent to the return statement or method signature in the sleepForTicks method to make the "magic numbers" self‑explaining.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/Global.java`:
- Around line 222-224: Add a short inline comment to the sleepForTicks(int)
method explaining the timeout formula used in the default argument (ticks * 800L
+ 2000), e.g., note that 800L represents a conservative ~800ms worst‑case per
game tick and the +2000 is a 2s safety buffer to handle server lag; place the
comment adjacent to the return statement or method signature in the
sleepForTicks method to make the "magic numbers" self‑explaining.
In
`@runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickDispatcher.java`:
- Around line 41-43: Add a shutdown method to TickDispatcher that unregisters
the retained subscribers: implement public void shutdown() {
eventBus.unregister(gameTickSubscriber);
eventBus.unregister(gameStateChangedSubscriber); } so tests and shutdown logic
can call TickDispatcher.shutdown() to cleanly remove the subscribers stored in
gameTickSubscriber and gameStateChangedSubscriber; ensure the method is public
and idempotent (safe to call multiple times).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5b129eab-43b0-41b4-83fa-0161c4fb68c9
📒 Files selected for processing (3)
runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/Global.javarunelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickDispatcher.javarunelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickWaiter.java
🚧 Files skipped from review as they are similar to previous changes (1)
- runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tick/TickWaiter.java
Summary by cubic
Introduce a tick-synchronized execution system that aligns script loops and waits with game ticks to cut latency and drift. Sleeps now wake on tick boundaries; new APIs let scripts run exactly once per tick with wall-clock timeouts and safe logout cleanup.
New Features
util/tick/TickDispatcher,TickListener, andTickWaiterfor tick-based dispatch and waits with wall-clock timeouts and logout cleanup.Global.sleepUntilnow wakes on tick boundaries (with fallback); addedsleepUntilNextTick()andsleepForTicks(int[, long]).Script.scheduleOnGameTick(Runnable)to run logic once per game tick on a script thread.Microbotnow injectsTickDispatcherfor global access.BlockingEventManager.isBlocking()exposes read-only blocking status (safe on client thread).CLAUDE.mdwith usage docs and examples.Migration
sleepUntil.scheduleOnGameTickto run exactly per tick.Written for commit 16746f4. Summary will update on new commits.
Summary by CodeRabbit
New Features
Documentation